<template>
  <div>
    <template v-if="list && list.length">
      <div class="virtual-list" :style="compStyle" @scroll="scrollHandle">
        <div class="hidden-list" :style="hiddenStyle"></div>
        <div class="list" :style="listStyle" ref="content">
          <div
            class="item"
            v-for="(item, index) in list"
            :key="`${item[identifier] || index}`"
            :style="itemStyle"
          >
            <slot :item="item" :index="index"></slot>
          </div>
        </div>
      </div>
    </template>
    <template v-else>
      <no-data></no-data>
    </template>
  </div>
</template>

<script>
export default {
  props: {
    height: {
      type: Number,
      default: 200,
    },
    itemHeight: {
      type: Number,
      default: 50,
    },
    sourceList: {
      type: Array,
      default() {
        return [];
      },
    },
    overSize: {
      //超出的个数
      type: Number,
      default: 2,
    },
    identifier: {
      type: String,
      default: 'id',
    },
  },
  data() {
    return {
      startIndex: 0,
      endIndex: 0,
      scrollTop: 0,
    };
  },
  computed: {
    // 容器高度
    compStyle() {
      return { height: `${this.height}px` };
    },
    // 列表的总高度
    hiddenStyle() {
      return { height: `${this.itemHeight * this.sourceList.length}px` };
    },

    // 单个item的高度
    itemStyle() {
      return { height: `${this.itemHeight}px` };
    },

    listStyle() {
      return { transform: `translateY(${this.scrollTop}px)` };
    },

    // 需要显示的列表数据
    list() {
      return this.sourceList.slice(
        this.startIndex,
        Math.min(this.endIndex, this.sourceList.length)
      );
    },

    // 页面可显示的个数
    viewCount() {
      return Math.ceil(this.height / this.itemHeight);
    },
  },
  methods: {
    init() {
      this.startIndex = 0;
      this.endIndex = this.viewCount + this.overSize * 2;
      this.scrollTop = 0;
      const timer = requestAnimationFrame(() => {
        if (this.$refs.content) {
          this.$refs.content.scrollIntoView({
            behavior: 'smooth',
          });
          cancelAnimationFrame(timer);
        }
      });
    },

    // scroll事件
    scrollHandle(e) {
      const top = e.target.scrollTop;
      this.startIndex = Math.floor(top / this.itemHeight);
      this.endIndex = this.startIndex + this.viewCount + this.overSize * 2;
      this.scrollTop = top - (top % this.itemHeight);
    },
  },
  watch: {
    sourceList: {
      deep: true,
      immediate: true,
      handler(newVal) {
        if (newVal && newVal.length) {
          this.init();
        }
      },
    },
  },
};
</script>

<style lang="scss" scoped>
.virtual-list {
  position: relative;
  width: 100%;
  min-height: 100px;
  overflow: hidden auto;
  .list {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    .item {
      overflow: hidden auto;
    }
  }
}
</style>
